﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using RayDen.Core.Primitives;
using RayDen.Data;
using RayDen.Entity.Interface;
using RayDen.Entity.Scene;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;

using RayDen.Sandbox;
using InfiniteLight = RayDen.RayEngine.Data.InfiniteLight;
using TriangleMesh = RayDen.RayEngine.Data.TriangleMesh;

namespace RayDen.RayEngine {



    public class RayEngineScene3 {
        private RgbSpectrum skyEmission = new RgbSpectrum(0f, 0f, 0.009f), groundReflection = new RgbSpectrum(0.06f);
        private new Dictionary<string, string[]> texturesMap;
        private bool noMeshLights;
        public bool AreaLightAsMesh = true;

        public SceneGeometryInfo SceneGeometry;
        public IMaterialProvider MaterialProvider;
        public BsdfLibrary MatLib;
        public TextureAtlas Textures;


        public ILight[] Lights;
        //public List<TriangleInfo> Triangles;
        public List<TriangleDataInfo> Triangles;
        public List<ITriangleMesh> Meshes;
        public List<ICamera> Cameras;
        public List<MeshVertex> MeshVertices;

        public Vector[] Vertices
        {
            get { return SceneGeometry.Vertices; }
        }

        public VolumeIntegrator VolumeIntegrator;

        //TODO Not initialized properly
        public int ShadowRayCount { get; set; }
        public int MaxPaths { get; set; }

        public int MaxPathDepth;
        public float RussianRuletteImportanceCap;
        public RgbSpectrum DefaultLightGain { get; set; }
        public ICamera Camera;
        //TODO

        public InfiniteLight EnvironmentMap;
        Dictionary<int,ITriangleMesh> meshTriangleMap;

        private SceneDataInfo sceneData;
        public RayEngineScene3(SceneGeometryInfo geometry, IMaterialProvider materials, ILight[] lights = null) {
            this.MaterialProvider = materials;
            this.SceneGeometry = geometry;
            Triangles = new List<TriangleDataInfo>();
            Meshes = new List<ITriangleMesh>();
            meshTriangleMap = new Dictionary<int, ITriangleMesh>();
            this.Lights = lights;
            this.BuildSceneData();
        }

        private void BuildSceneData() {
            int verticesCount = 0;
            this.sceneData  = new SceneDataInfo(SceneGeometry);
            this.Triangles = sceneData.Triangles.ToList();
            this.Meshes.AddRange(sceneData.Meshes);

            /*
            foreach (var geometryInfo in SceneGeometry.Geometry) {
                var mesh = new TriangleMesh() {
                    MeshProfile = this.CreateGeometryProfile(geometryInfo),
                    MaterialName = geometryInfo.MaterialName,
                    MeshName = geometryInfo.Name,
                    Indexes = geometryInfo.IndexData.ToArray()
                };
                this.Meshes.Add(mesh);

                var model = geometryInfo;
                var delta = 0;
                var index = 0;
                var a = -1f;
                bool hasNormals = false;
                Normal[] norms = null;
                int[] ni = null;

                bool hasTexCoords = false;
                UV[] texs = null;
                int[] ti = null;
                var startTriangle = Math.Max(0, Triangles.Count);

                if (model.IndexData.Any()) {
                    hasNormals = model.NormalIndexData != null && model.NormalIndexData.Any();
                    hasTexCoords = model.TextureIndexData != null && model.TextureIndexData.Any();
                    if (hasTexCoords) {
                        texs = SceneGeometry.TexCoords.ToUVArray();
                        if (!texs.Any())
                            hasTexCoords = false;
                        ti = model.TextureIndexData.ToArray();
                    }
                    if (hasNormals) {
                        norms = SceneGeometry.Normals.Select(item => new Normal(item)).ToArray();
                        ni = model.NormalIndexData.ToArray();
                    }

                    for (int i = 0; i < model.IndexData.Count; i += 3) {
                        index++;
                        var newTriangle = new TriangleInfo(model.IndexData[i] - delta, model.IndexData[i + 1] - delta,
                                                           model.IndexData[i + 2] - delta);
                        Triangles.Add(newTriangle);
                        //meshIndexes.Add(extMesh);
                        if (hasTexCoords) {
                            var triIndex = index;
                            var t1 = texs[(ti[i] - 1)];
                            var t2 = texs[(ti[i + 1] - 1)];
                            var t3 = texs[(ti[i + 2] - 1)];
                            mesh.texCoords.Add(startTriangle + triIndex-1, new[] {t1, t2, t3});
                        }
                        if (hasNormals) {
                            //Add vertice normal
                            var triIndex = index;
                            var norm1 = a*norms[(ni[i] - 1)];
                            var norm2 = a*norms[(ni[i + 1] - 1)];
                            var norm3 = a*norms[(ni[i + 2] - 1)];
                            mesh.normals.Add(startTriangle + triIndex - 1, new[] { norm1, norm2, norm3 });
                        }
                        else {
                            var triIndex = index;
                            var norm1 = newTriangle.ComputeVerticeNormal(0, SceneGeometry.Vertices);
                            var norm2 = newTriangle.ComputeVerticeNormal(1, SceneGeometry.Vertices);
                            var norm3 = newTriangle.ComputeVerticeNormal(2, SceneGeometry.Vertices);
                            mesh.normals.Add(startTriangle + triIndex - 1, new[] { norm1, norm2, norm3 });
                        }
                    }
                 
                //extMesh.Indexes = model.IndexBuffer.Select(item => item - (extMesh.StartVertice - 1)).ToArray();
                }
                else {
                    for (int i = 0; i <SceneGeometry. Vertices.Length; i += 3) {
                        var tri = new TriangleInfo(Array.IndexOf(SceneGeometry.Vertices, SceneGeometry.Vertices[i]),
                                                   Array.IndexOf(SceneGeometry.Vertices, SceneGeometry.Vertices[i + 1]),
                                                   Array.IndexOf(SceneGeometry.Vertices, SceneGeometry.Vertices[i + 2]));
                        Triangles.Add(tri);
                        //meshIndexes.Add(extMesh);
                        index++;
                    }
                }
                var endTriangle = Triangles.Count;
                verticesCount += (model.IndexData.Max() - model.IndexData.Min());
                mesh.StartVertice = model.IndexData.Min();
                mesh.EndVertice = model.IndexData.Max();
                mesh.StartTriangle = startTriangle;
                mesh.EndTriangle = endTriangle;
                mesh.VerticesOffset = verticesCount;

                for (int n = startTriangle; n < endTriangle; n++) 
                    meshTriangleMap.Add(n, mesh);
            }
            */
            //Lightsources 
            if (Lights != null)
            {
                if (!Lights.OfType<MeshLight>().Any())
                {
                    foreach (var triangleMesh in Meshes)
                    {
                        if (triangleMesh.MeshProfile is LightsourceProfile)
                        {
                            triangleMesh.MeshProfile = new SurfaceProfile() {MaterialName = triangleMesh.MaterialName};
                        }
                    }
                }

                if (AreaLightAsMesh)
                {
                    foreach (var lightsource in Lights.OfType<MeshLight>())
                    {
                       // lightsource.Initialize(this);
                    }
                }
                else
                {
                    var triLights = new List<TriangleLight>();
                    var meshLights = Lights.OfType<MeshLight>().ToArray();
                     
                    foreach (var meshLight in meshLights)
                    {
                        MeshLight light = meshLight;
                        var mesh = Meshes.FirstOrDefault(item => item.MeshName.Equals(light.LightName));
                        if (mesh != null)
                        {
                            for (int st = mesh.StartTriangle; st < mesh.EndTriangle; st++)
                            {
                                //triLights.Add(new TriangleLight(this, st, mesh, light.gain));
                            }
                        }
                    }
                    this.Lights = this.Lights.Except(meshLights).Union(triLights).ToArray();
                }
                noMeshLights = !(this.Lights.Any(item => item is MeshLight || item is TriangleLight));
            }
            //Textures
            if (MaterialProvider  != null)
                this.LoadTextures();
        }

        private void LoadTextures() {
            Tracer.TraceLine("Start loading textures");
            this.texturesMap = new Dictionary<string, string[]>();
            this.Textures = new TextureAtlas();

            foreach (var materialInfo in this.MaterialProvider.GetAll()) {
                var textures = new List<string>();

                if (materialInfo.Diffuse != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.Diffuse, textures);
                }

                if (materialInfo.BumpMap != null)
                {
                    ProcessMaterialTexture((ImageTextureInfo)materialInfo.BumpMap, textures);
                }

                if (textures.Any())
                    texturesMap.Add(materialInfo.Name.ToLower(), textures.ToArray());
            }
            Tracer.TraceLine("Complete loading textures");
        }

        private void ProcessMaterialTexture(ImageTextureInfo diff, List<string> textures)
        {
            var texPath = this.FindTexture(diff);
            if (texPath != null)
            {
                this.Textures.AddTexture(new ImageTextureInfo()
                    {
                        FilePath = texPath,
                        Name = Path.GetFileName(texPath)
                    });
                textures.Add(Path.GetFileName(texPath));
            }
        }

        public string FindTexture(ImageTextureInfo ii)
        {
            string filePath = ii.FilePath;
            if (File.Exists(filePath))
                return filePath;
            if (File.Exists(ii.Name))
            {
                return Directory.GetCurrentDirectory() + @"\" + ii.Name;
            }
            return null;
        }

        public ITexture Query(string material, int tex) {
            if (!texturesMap.ContainsKey(material))
                return null;
            var map = texturesMap[material];
            if (map == null || map.Length <= tex)
                return null;
            return this.Textures[texturesMap[material][tex]];
        }


        internal PathTracer.Volume.VolumeComputation GetVolumeComputation() {
            return new VolumeComputation(this.VolumeIntegrator);
        }


        public ITriangleMesh GetMeshByTriangleIndex(int currentTriangleIndex)
        {
            return sceneData.GetMeshByTriangleIndex(currentTriangleIndex);
            ITriangleMesh m;
            meshTriangleMap.TryGetValue(currentTriangleIndex, out m);
            if (!m.TriangleInRange(currentTriangleIndex))
            {
                Debugger.Break();
            }
            return m;
            for (int index = 0; index < Meshes.Count; index++) {
                if (Meshes[index].TriangleInRange(currentTriangleIndex))
                    return Meshes[index];
            }
            return null;
        }

        public bool IsLight(int currentTriangleIndex)
        {
            if (noMeshLights)
                return false;

            for (int index = 0; index < Meshes.Count; index++) {
                if (Meshes[index].TriangleInRange(currentTriangleIndex) && (Meshes[index].MeshProfile is LightsourceProfile))
                    return true;
            }
            return false;
        }

        TriangleLight FindLight(int triangleIndex) {
            return this.Lights.OfType<TriangleLight>().FirstOrDefault(item => item.triangleIndex.Equals(triangleIndex));
        }

        public ILight GetLightByIndex(int currentTriangleIndex)
        {
            for (int index = 0; index < Meshes.Count; index++) {
                if (Meshes[index].TriangleInRange(currentTriangleIndex) &&
                    (Meshes[index].MeshProfile is LightsourceProfile)) {

                    ILight profileLight = ((Meshes[index].MeshProfile as LightsourceProfile).Light) != null ? (ILight) ((Meshes[index].MeshProfile as LightsourceProfile).Light) : FindLight(currentTriangleIndex);
                    return profileLight;
                }
            }
            return null;
        }

        public int SampleLights(float p, out float lightStrategyPdf)
        {
            int lightIndex = Math.Min((int)Math.Floor(Lights.Length * p), Lights.Length - 1);
            lightStrategyPdf = 1f / Lights.Length;
            return lightIndex;
        }

        public int SampleLights(float p)
        {
            int lightIndex = Math.Min((int)Math.Floor(Lights.Length * p), Lights.Length - 1);
            return lightIndex;
        }

        public RgbSpectrum SampleEnvironment(Vector dir)
        {
            //return RgbSpectrum.ZeroSpectrum();
            if (EnvironmentMap != null) {
                return new RgbSpectrum(EnvironmentMap.Le(ref dir));
            }
            if (dir.y > 0)
                return skyEmission;
            return groundReflection;
        }
    }
}
